/**
 * 本章小节
 *  组件的拆分
 *  定义属性
 * 编写组件的基本模板和 入口，尝试放到demo中去使用
 * 根据定义的属性，拆解组件功能，去依次实现
 * bug的修复
 * 代码的优化和严谨性
 * 组件的 设计 可以以尝试画uml图
 *
 */
import React from 'react';
import SwiperItem from '@/bases/swiper/swiper-item';
import SwiperPageIndicator from '@/bases/swiper/swiper-page-indicator';
import { modulus } from '@/bases/swiper/utils';
import './styles/swiper.scss';
export interface SwiperProps {
  // 是否循环播放
  loop?: boolean;
  // 是否自动播放
  autoplay?: boolean;
  autoPlayInterval?: number; // 自动播放间隔时间
  // 分页
  showIndicator?: boolean;
  indicatorClassName?: string;
  // 默认显示第几张
  defaultIndex?: number;
  children?: React.ReactElement | React.ReactElement[];
  style?: React.CSSProperties & Partial<Record<'--height' | '--width' | '--border-radius' | '--track-padding', string>>;
}
const classPrefix = 'ygm-swiper';
/**
 * Swiper组件，用于创建一个轮播图组件
 * @param {SwiperProps} props - 组件的props
 * @returns {JSX.Element} - 渲染的Swiper组件
 */
const Swiper: React.FC<SwiperProps> = (props) => {
  // 初始化当前索引，如果props中提供了defaultIndex，则使用它，否则默认为0
  const [currentIndex, setCurrentIndex] = React.useState<number>(props.defaultIndex || 0);
  const [dragging, setDragging] = React.useState<boolean>(false);
  // 初始化触摸开始的位置
  const startRef = React.useRef<number>(0); // 鼠标位止初始值
  const slideRatioRef = React.useRef<number>(0); // 百分比
  const trackRef = React.useRef<HTMLDivElement>(null);
  const autoPlaying = React.useRef<boolean>(false); // 是否自动播放
  const intervalRef = React.useRef<number>(0);

  const { validChidren, count } = React.useMemo(() => {
    let count = 0;
    const validChidren = React.Children.map(props.children, (child) => {
      // 验证对象是否是一个React元素
      if (!React.isValidElement(child)) return null;
      // 验证是否是一个 SwiperItem类型
      if (child.type !== SwiperItem) {
        console.warn('Swiper组件的子组件必须是SwiperItem类型');
      }
      count++;
      return child;
    });
    return { validChidren, count };
  }, [props.children]);

  /**
   * 计算最终位置
   *
   * 该函数根据当前索引和目标索引来计算滑动后的最终位置
   * 它通过将当前索引乘以-100，然后加上目标索引乘以100的结果来实现
   * 这种计算方式是为了在界面上实现一个滑动视口的效果，其中每个视口的宽度被认为是100个单位
   *
   * @param index 目标索引，表示希望滑动到的位置
   * @returns 最终位置，以单位长度表示
   */
  const getFinalPosition = (index: number) => {
    // 计算最终位置，通过当前索引和目标索引的对比来确定
    let finalPosition = -currentIndex * 100 + index * 100;
    // 如果不启用循环模式，直接返回计算出的最终位置
    if (!props.loop) return finalPosition;
    // 如果启用了循环模式，则需要进行特殊处理
    const totalWidth = count * 100;
    // 无限轮播，当前图的前后平均分配轮播数量
    const flagWidth = totalWidth / 2;
    finalPosition = modulus(finalPosition + flagWidth, totalWidth) - flagWidth;

    return finalPosition;
  };
  const getTransition = (position: number) => {
    if (dragging) {
      return '';
    } else if (autoPlaying.current) {
      if (position === 100 || position === 0) {
        return 'tranfrom 0.3s ease-out';
      } else {
        return '';
      }
    } else if (position < -100) {
      return '';
    }

    return 'tranfrom 0.3s ease-out';
  };

  /**
   * 渲染轮播项组件
   *
   * 此函数负责根据子组件渲染轮播项每个轮播项的位置由其索引决定
   * 它使用了React.Children.map来遍历props.children中的每个子组件，并将其包装在带有特定样式的div中
   * 这些样式包括基于索引的left值和根据getFinalPosition函数计算得出的transform属性值
   */
  const renderSwiperItem = () => {
    return (
      <div className={`${classPrefix}-track-inner`}>
        {React.Children.map(validChidren, (child, index) => {
          const position = getFinalPosition(index);
          return (
            <div
              className={`${classPrefix}-slide`}
              key={index}
              style={{
                left: `-${index * 100}%`,
                transform: `translate3d(${position}%,0,0)`,
                transition: getTransition(position),
              }}
            >
              {child}
            </div>
          );
        })}
      </div>
    );
  };
  // 百分比
  const getSlideRatio = (diff: number) => {
    const element = trackRef.current;
    if (!element) return 0;
    return diff / element.offsetWidth;
  };

  // 边界值
  const boundIndex = React.useCallback(
    (currentIndex: number) => {
      let min = 0;
      let max = count - 1; // 轮播值数量减去1
      let ret = currentIndex;
      ret = Math.max(currentIndex, min);
      ret = Math.min(currentIndex, max);
      return ret;
    },
    [count]
  );
  const swipeTo = React.useCallback(
    (index: number) => {
      const targetIndex = props.loop ? modulus(index, count) : boundIndex(index);
      setCurrentIndex(targetIndex);
    },
    [boundIndex, count, props.loop]
  );
  const swipeNext = React.useCallback(() => {
    swipeTo(currentIndex + 1);
  }, [swipeTo, currentIndex]);

  const onTouchEnd = () => {
    const index = Math.round(slideRatioRef.current);
    slideRatioRef.current = 0;
    const positon = currentIndex + index;
    swipeTo(positon);
    setDragging(false);
    document.removeEventListener('touchmove', onTouchMove);
    document.removeEventListener('touchend', onTouchEnd);
  };
  const onTouchMove = (e: TouchEvent) => {
    // 鼠标点击的初始位置
    const currentX = e.changedTouches[0].clientX;
    // 滑动和初始点击值的差值
    const diff = startRef.current - currentX;
    slideRatioRef.current = getSlideRatio(diff);
    // 当前位置
    let positon = currentIndex + slideRatioRef.current;
    if (!props.loop) {
      positon = boundIndex(positon);
    }
    setCurrentIndex(positon);
  };
  /**
   * 当触摸屏触摸开始时触发的事件处理函数
   *
   * @param {React.TouchEvent<HTMLDivElement>} e - 触摸事件对象，包含触摸事件的相关信息
   */
  const onTouchStart = (e: React.TouchEvent<HTMLDivElement>) => {
    // 鼠标获取触摸开始时的坐标
    startRef.current = e.changedTouches[0].clientX;
    setDragging(true);
    clearInterval(intervalRef.current);
    autoPlaying.current = false; // 暂停自动播放
    document.addEventListener('touchmove', onTouchMove);
    document.addEventListener('touchend', onTouchEnd);
  };
  React.useEffect(() => {
    if (!props.autoplay || dragging) return;
    intervalRef.current = window.setInterval(() => {
      swipeNext();
    }, props.autoPlayInterval);
    return () => {
      clearInterval(intervalRef.current);
    };
  }, [dragging, props.autoPlayInterval, props.autoplay, swipeNext]);
  if (count === 0 || !validChidren) {
    return null;
  }
  // 返回Swiper组件的主体结构
  return (
    <div className={classPrefix} style={props.style}>
      <div className={`${classPrefix}-track`} onTouchStart={onTouchStart} ref={trackRef}>
        {renderSwiperItem()}
      </div>
      {props.showIndicator && (
        <div className={`${classPrefix}-indicator`}>
          <SwiperPageIndicator
            total={count}
            current={slideRatioRef.current > 0 ? Math.floor(currentIndex) : Math.ceil(currentIndex)}
            indicatorClassName={props.indicatorClassName}
          />
        </div>
      )}
      {/* <div className={`${classPrefix}-track`}>{props.children}</div> */}
    </div>
  );
};
export default Swiper;
Swiper.defaultProps = {
  autoplay: false,
  defaultIndex: 0,
  loop: false,
  showIndicator: true,
  autoPlayInterval: 3000,
};
Swiper.displayName = 'Swiper';
